﻿using System;
using System.Collections.Generic;


namespace advadev.CanOpen
{

    /// <summary>
    /// Represents a can open real 32
    /// </summary>
    public class CanOpenReal32 : ICanOpenValueObject
    {

        /// <summary>for internal use</summary>
        private float _value;


        /// <summary>
        /// Initializes a new instance of the CanOpenReal32 class by the given
        /// parameter
        /// </summary>
        /// <param name="index">
        /// Index of the object
        /// </param>
        /// <param name="subIndex">
        /// Sub index of the object
        /// </param>
        /// <param name="isReadable">
        /// Flag indicating whether the object is readable
        /// </param>
        /// <param name="isWriteable">
        /// Flag indicating whether the object is writeable
        /// </param>
        public CanOpenReal32(UInt16 index, byte subIndex, bool isReadable, bool isWriteable)
        {
            ByteLength = 4;
            BitLength = 32;
            Index = index;
            SubIndex = subIndex;
            IsReadable = isReadable;
            IsWriteable = isWriteable;
            ByteOrder = CanOpenByteOrder.LittleEndian;
            LastServiceDataObjectUploadSequence = new List<CanOpenMessage>();
            LastServiceDataObjectDownloadSequence = new List<CanOpenMessage>();
        }


        /// <summary>
        /// Gets or sets the value
        /// </summary>
        /// <remarks>
        /// The value will be shifted until it is in the range between
        /// MinValue..MaxValue
        /// </remarks>
        public float Value
        {
            get
            {
                return _value;
            }
            set
            {
                if (value != _value)
                {
                    float oldValue = _value;
                    _value = value;
                    onRaiseValueChanged(new ValueChangedEventArgs(oldValue, _value));
                }
            }
        }

        /// <summary>
        /// Gets the minimum value
        /// </summary>
        public float MinValue
        {
            get
            {
                return float.MinValue;
            }
        }

        /// <summary>
        /// Gets the maximum value
        /// </summary>
        public float MaxValue
        {
            get
            {
                return float.MaxValue;
            }
        }

        /// <summary>
        /// Gets the byte length
        /// </summary>
        internal uint ByteLength
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the bit length
        /// </summary>
        internal uint BitLength
        {
            get;
            private set;
        }


        /// <summary>
        /// Raises the value changing event
        /// </summary>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void onRaiseValueChanging(ValueChangingEventArgs args)
        {
            if (_valueChanging != null)
            {
                _valueChanging(this, args);
            }
        }

        /// <summary>
        /// Raises the value changed event
        /// </summary>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void onRaiseValueChanged(ValueChangedEventArgs args)
        {
            if (_valueChanged != null)
            {
                _valueChanged(this, args);
            }
        }


        #region ICanOpenValueObject implementation
        /// <summary>for internal use</summary>
        private event ValueChangingEventHandler _valueChanging;
        /// <summary>for internal use</summary>
        private event ValueChangedEventHandler _valueChanged;
        /// <summary>for internal use</summary>
        private bool _isValueChangedEventRaisingPaused;
        /// <summary>for internal use</summary>
        private float _oldValue;

        /// <summary>
        /// Occures when the value is changing
        /// </summary>
        public event ValueChangingEventHandler ValueChanging
        {
            add
            {
                _valueChanging += value;
            }
            remove
            {
                _valueChanging -= value;
            }
        }

        /// <summary>
        /// Occures when the value is changed
        /// </summary>
        public event ValueChangedEventHandler ValueChanged
        {
            add
            {
                _valueChanged += value;
            }
            remove
            {
                _valueChanged -= value;
            }
        }

        /// <summary>
        /// Gets the index of the object
        /// </summary>
        public UInt16 Index
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the sub index of the object
        /// </summary>
        public byte SubIndex
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets a flag indicating whether the object is readable
        /// </summary>
        public bool IsReadable
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a flag indicating whether the object is writeable
        /// </summary>
        public bool IsWriteable
        {
            get;
            set;
        }

        /// <summary>
        /// Gets a flag indicating whether a expedited download transfer is
        /// possible for this object
        /// </summary>
        bool ICanOpenValueObject.IsExpeditedDownloadTransferPossible
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets a flag indicating whether a expedited upload transfer is
        /// possible for this object
        /// </summary>
        bool ICanOpenValueObject.IsExpeditedUploadTransferPossible
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets a flag indicating whether a block download transfer is advised
        /// for this object
        /// </summary>
        bool ICanOpenValueObject.IsBlockDownloadTransferAdvised
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets a flag indicating whether a block upload transfer is advised
        /// for this object
        /// </summary>
        bool ICanOpenValueObject.IsBlockUploadTransferAdvised
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets or sets a flag indicating whether the value changed event
        /// raising is paused
        /// </summary>
        public bool IsValueChangedEventRaisingPaused
        {
            get
            {
                return _isValueChangedEventRaisingPaused;
            }
            set
            {
                if (value != _isValueChangedEventRaisingPaused)
                {
                    _isValueChangedEventRaisingPaused = value;
                    if (!_isValueChangedEventRaisingPaused)
                    {
                        if (_value != _oldValue)
                        {
                            onRaiseValueChanged(new ValueChangedEventArgs(_oldValue, _value));
                        }
                        _oldValue = _value;
                    }
                    else
                    {
                        _oldValue = _value;
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the byte order
        /// </summary>
        public CanOpenByteOrder ByteOrder
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the last service data object upload sequence
        /// </summary>
        public IList<CanOpenMessage> LastServiceDataObjectUploadSequence
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the last service data object download sequence
        /// </summary>
        public IList<CanOpenMessage> LastServiceDataObjectDownloadSequence
        {
            get;
            private set;
        }


        /// <summary>
        /// Converts the value of the object to a byte array
        /// </summary>
        /// <returns>
        /// Value of the object converted to a byte array
        /// </returns>
        IList<byte> ICanOpenValueObject.ToByteArray()
        {
            byte[] bytes = BitConverter.GetBytes(Value);
            IList<byte> result = new List<byte>();
            //for (int i = 0; i < ByteLength; i++)
            //{
            //    result.Add(bytes[i]);
            //}
            if (ByteOrder == CanOpenByteOrder.LittleEndian)
            {
                for (int i = 0; i < ByteLength; i++)
                {
                    result.Add(bytes[i]);
                }
            }
            else
            {
                for (int i = (int)(ByteLength - 1); i >= 0; i--)
                {
                    result.Add(bytes[i]);
                }
            }
            return result;
        }

        /// <summary>
        /// Converts the given byte array and sets the result as value
        /// </summary>
        /// <param name="byteArray">
        /// Byte array to convert and set as value
        /// </param>
        void ICanOpenValueObject.FromByteArray(IList<byte> byteArray)
        {
            try
            {
                byte[] bytes = new byte[4];
                for (int i = 0; i < byteArray.Count; i++)
                {
                    //bytes[i] = byteArray[i];
                    if (ByteOrder == CanOpenByteOrder.LittleEndian)
                    {
                        bytes[i] = byteArray[i];
                    }
                    else
                    {
                        bytes[i] = byteArray[(byteArray.Count - 1) - i];
                    }
                }
                float oldValue = _value;
                _value = BitConverter.ToSingle(bytes, 0);
                if (_value != oldValue)
                {
                    if (!IsValueChangedEventRaisingPaused)
                    {
                        onRaiseValueChanged(new ValueChangedEventArgs(oldValue, _value));
                    }
                }
            }
            catch { }
        }

        /// <summary>
        /// Converts the given byte array and sets the result as value after
        /// validating the value
        /// </summary>
        /// <param name="byteArray">
        /// Byte array to convert and set as value
        /// </param>
        /// <param name="abortCode">
        /// Abort code if the validation failed (otherwise null)
        /// </param>
        /// <returns>
        /// True, if the new value is valid, false otherwise
        /// </returns>
        bool ICanOpenValueObject.FromByteArray(IList<byte> byteArray, out CanOpenAbortCode abortCode)
        {
            try
            {
                byte[] bytes = new byte[4];
                for (int i = 0; i < byteArray.Count; i++)
                {
                    //bytes[i] = byteArray[i];
                    if (ByteOrder == CanOpenByteOrder.LittleEndian)
                    {
                        bytes[i] = byteArray[i];
                    }
                    else
                    {
                        bytes[i] = byteArray[(byteArray.Count - 1) - i];
                    }
                }
                float newValue = BitConverter.ToSingle(bytes, 0);
                ValueChangingEventArgs args = new ValueChangingEventArgs(_value, newValue);
                onRaiseValueChanging(args);
                if (args.AbortCode != null)
                {
                    abortCode = args.AbortCode;
                    return false;
                }
                float oldValue = _value;
                _value = newValue;
                if (_value != oldValue)
                {
                    if (!IsValueChangedEventRaisingPaused)
                    {
                        onRaiseValueChanged(new ValueChangedEventArgs(oldValue, _value));
                    }
                }
            }
            catch
            {
                abortCode = new CanOpenAbortCode(0x30, 0x00, 0x09, 0x06);
                return false;
            }
            abortCode = null;
            return true;
        }
        #endregion ICanOpenValueObject implementation

    }

}
